using System;
using System.Text;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Drawing.Imaging;
using System.Drawing;
using System.Diagnostics ;
using AppliedRecognition.Util;
using System.ComponentModel;

namespace AppliedRecognition.Speech.TaskSupport {
	
	/// <summary>
	/// Provides details about a Window returned by the 
	/// enumeration
	/// </summary>
	/// 
	[Serializable]
	public sealed class WindowItem:IDisposable, IComparable  
	{
		private ARTraceSwitch CommandTS= new ARTraceSwitch("Command", "Actions");
		private const uint TIMEOUT = 250;
	
		#region Ctor GetHashCode

		private static IntPtr _applicationhandle;
		public static IntPtr ApplicationHandle
		{
			get 
			{
				return _applicationhandle;
			}
			set 
			{
				_applicationhandle = value;
			}
		}

		//		[NonSerialized]
		private IntPtr hWnd = IntPtr.Zero;
		private IntPtr hWndOwner = IntPtr.Zero;
		/// <summary>
		///  Constructs a new instance of this class for
		///  the specified Window Handle.
		/// </summary>
		/// <param name="hWnd">The Window Handle</param>
		public WindowItem(IntPtr hWnd) 
		{
			this.hWnd = hWnd;
			GC.SuppressFinalize(this); 
		}

		public WindowItem(IntPtr hWnd,IntPtr hWndOwner ) 
		{
			this.hWnd = hWnd;
			this.hWndOwner = hWndOwner;
			GC.SuppressFinalize(this); 
		}

		//		public WindowItem() {}

		/// <summary>
		/// To allow items to be compared, the hash code
		/// is set to the Window handle, so two EnumWindowsItem
		/// objects for the same Window will be equal.
		/// </summary>
		/// <returns>The Window Handle for this window</returns>
		public override System.Int32 GetHashCode() 
		{
			return this.hWnd.ToInt32();
		}

		public override bool Equals(object obj) 
		{
			WindowItem o = (WindowItem)obj;
			bool ret = (this.Handle == o.Handle );

			return ret;
		}

		public int CompareTo(object obj)
		{
			WindowItem b = (WindowItem) obj;
			if (this.Handle == b.Handle ) return 0;
			return this.Text.CompareTo(b.Text);
		}

		/// <summary>
		/// Gets the window's handle
		/// </summary>
		internal IntPtr Handle 
		{
			get 
			{
				return this.hWnd;
			}
		}
		internal IntPtr OwnerHandle 
		{
			get	
			{
				if (hWndOwner == IntPtr.Zero)
					hWndOwner = PWin32.GetWindow(Handle,(uint)GW.OWNER);
				return hWndOwner; 
			}
		}

		#endregion

		#region Class and Text
		/// <summary>
		/// Gets the window's title (caption)
		/// </summary>
		/// 
		private string _text = null;
		public string Text 
		{
			get 
			{
				if (_text == null) 
				{
					int len = PWin32.GetWindowTextLength(Handle);
					if (len ==0) { return "" ; }
					len++;
					StringBuilder title = new StringBuilder(len,len);
					PWin32.GetWindowText(Handle , title, title.Capacity);
					_text  =  title.ToString();
				}
				return _text;
			}
		}

		/// <summary>
		/// Gets the window's class name.
		/// </summary>
		/// 
		private string _classname= null;
		internal string ClassName 
		{
			get 
			{
				if (_classname == null) 
				{
					StringBuilder className = new StringBuilder(260, 260);
					int ret = PWin32.GetClassName(this.hWnd, className,className.Capacity);
					if (ret==0) 
					{
						System.ComponentModel.Win32Exception ex = new System.ComponentModel.Win32Exception();
						CommandTS.WError("Class name is null: " + ex.ToString());
						className.Length = 0;
					}

					_classname =  className.ToString();
				}
				return _classname;
			}
		}
		#endregion

		#region Visibility

		/// <summary>
		/// Gets/Sets whether the window is iconic (mimimised) or not.
		/// </summary>
		/// 
		private bool _iconic;
		private object oIconic = null;
		internal bool Iconic 
		{
			get 
			{
				if (oIconic == null) 
				{
					_iconic = ((PWin32.IsIconic(Handle) == 0) ? false : true);
					oIconic = true;
				}
				return _iconic; 
			}
			set 
			{
				oIconic = null;
				IntPtr msg;

				msg = (IntPtr ) ((value)?SC.MINIMIZE:SC.RESTORE);

				PWin32.SendMessage(Handle,(int)	WM.SYSCOMMAND, 	msg ,IntPtr.Zero);
			}
		}
         
		/// <summary>
		/// Gets/Sets whether the window is maximised or not.
		/// </summary>
		internal bool Maximised 
		{
			get 
			{
				return ((PWin32.IsZoomed(Handle) == 0) ? false : true);
			}
			set 
			{
				IntPtr msg = (IntPtr ) ((value)?SC.MAXIMIZE:SC.RESTORE);
				PWin32.SendMessage(Handle,(int)WM.SYSCOMMAND,msg ,IntPtr.Zero);
			}
		}

		/// <summary>
		/// Gets whether the window is visible.
		/// </summary>
		/// 
		private bool _visible;
		private bool _visibleLoaded=false;
		internal bool Visible 
		{
			get 
			{
				if (_visibleLoaded  == false) 
				{
					_visible =  ((PWin32.IsWindowVisible(Handle) == 0) ? false : true);
					_visibleLoaded = true;
				}
				return _visible;
			}
		}
		#endregion

		#region 2D Info

		/// <summary>
		/// Gets the bounding rectangle of the window
		/// </summary>
		public System.Drawing.Rectangle Rect 
		{
			get 
			{	
				RECT rc = new RECT();
				int ret =PWin32.GetWindowRect(Handle,	ref rc);
				if (ret==0) { throw new System.ComponentModel.Win32Exception(); }
        
				System.Drawing.Rectangle rcRet = 
					new System.Drawing.Rectangle(rc.Left, rc.Top,	rc.Right - rc.Left, rc.Bottom - rc.Top);
				return rcRet;
			}
		}

		/// <summary>
		/// Gets the location of the window relative to the screen.
		/// </summary>
		public System.Drawing.Point Location 
		{
			get 
			{
				System.Drawing.Rectangle rc = Rect;
				System.Drawing.Point pt = new System.Drawing.Point(rc.Left,rc.Top);
				return pt;
			}
		}
         
		/// <summary>
		/// Gets the size of the window.
		/// </summary>
		public System.Drawing.Size Size 
		{
			get 
			{
				System.Drawing.Rectangle rc = Rect;
				System.Drawing.Size sz = new System.Drawing.Size(
					rc.Right - rc.Left,	rc.Bottom - rc.Top);
				return sz;
			}
		}
		#endregion

		#region WindowStyles

		private WindowStyleFlags _windowstyle;
		private bool _wsfLoaded = false;

		internal WindowStyleFlags WindowStyle 
		{
			get 
			{
				if (_wsfLoaded == false) 
				{
					_windowstyle = (WindowStyleFlags)PWin32.GetWindowLong(Handle ,(int) GWL.STYLE);
					if (_windowstyle ==0) { throw new System.ComponentModel.Win32Exception(); }
					_wsfLoaded = true;
				}
				return _windowstyle ;
			}
		}
      
		//		private WS_EX  _ewindowstyle;
		internal WS_EX ExtendedWindowStyle 
		{
			get 
			{
				uint ret = PWin32.GetWindowLong(Handle, (int)GWL.EXSTYLE);
				if (ret==0) 
				{ 
					CommandTS.WWarning("GetWindowLong(" + this.ToString() +" , (int)GWL.EXSTYLE) returned 0");
				}
				return (WS_EX)ret;
			}
		}

		internal WS_EX OwnerExtendedWindowStyle 
		{
			get 
			{
				IntPtr h =(OwnerHandle == IntPtr.Zero )?  Handle: OwnerHandle ;
				uint ret = PWin32.GetWindowLong(h, (int)GWL.EXSTYLE);
				// Don't check //if (ret==0) { throw new System.ComponentModel.Win32Exception(); }
				return (WS_EX)ret;
			}
		}


		public bool IsPopUp 
		{
			get {return ((this.WindowStyle & WindowStyleFlags.WS_POPUP) != 0); }
		}
		public bool IsExToolWindow 
		{
			get {return ((this.ExtendedWindowStyle & WS_EX.TOOLWINDOW) != 0); }
		}
		public bool IsOwnerExToolWindow 
		{
			get {return ((this.OwnerExtendedWindowStyle & WS_EX.TOOLWINDOW) != 0); }
		}

		public bool IsExAppWindow 
		{
			get {return ((this.ExtendedWindowStyle & WS_EX.APPWINDOW) != 0); }
		}
		public bool IsModalDialog 
		{
			get {return ((this.ExtendedWindowStyle & WS_EX.DLGMODALFRAME) != 0); }
		}

		public bool IsExControlParent 
		{
			get 
			{
				return ((this.ExtendedWindowStyle & WS_EX.CONTROLPARENT ) != 0); 
			}
		}

	
		public bool IsTopMost 
		{
			get {return ((this.ExtendedWindowStyle & WS_EX.TOPMOST) != 0); }
		}
	
	
		#endregion

		#region Icons
		
		private Icon _icon=null; 
		public Icon Icon 
		{
			get 
			{
				
				if ( _icon == null) 
				{
					_icon = Icon.FromHandle(IconHandle);
				}
				return _icon;
			}
		}

		private IntPtr hIcon = IntPtr.Zero; 
		internal IntPtr IconHandle 
		{ 
			get 
			{
				if (!IntPtr.Zero.Equals(hIcon)) return hIcon;
			
				uint timeout=500;
				uint msg = (uint) WM.GETICON;

				IntPtr h=(OwnerHandle == IntPtr.Zero )? Handle: OwnerHandle ;

				// big Icon
				PWin32.SendMessageTimeout(h, msg, (uint)	WMGetIconSize.ICON_BIG, 0, SMTO.ABORTIFHUNG, timeout, out hIcon); 
				if (!IntPtr.Zero.Equals(hIcon)) return hIcon;

				// small icon
				PWin32.SendMessageTimeout(h, msg,	(uint)	WMGetIconSize.ICON_SMALL, 0, SMTO.ABORTIFHUNG , timeout, out hIcon); 
				if (!IntPtr.Zero.Equals(hIcon)) return hIcon;

				//small icon 2
				PWin32.SendMessageTimeout(h, msg, (uint)	WMGetIconSize.ICON_SMALL2   , 0, SMTO.ABORTIFHUNG , timeout, out hIcon); 
				if (!IntPtr.Zero.Equals(hIcon)) return hIcon;
				
				hIcon = PWin32.GetClassLongPtr(h , (int)WMGetIconSize.GCL_HICON); 
				if (!IntPtr.Zero.Equals(hIcon)) return hIcon;

				hIcon = PWin32.GetClassLongPtr(h , (int)WMGetIconSize.GCL_HICONSM); 
				if (!IntPtr.Zero.Equals(hIcon)) return hIcon;

				PWin32.SendMessageTimeout(h , (uint)WM.QUERYDRAGICON, 0, 0, 0, timeout, out hIcon); 
				if (!IntPtr.Zero.Equals(hIcon)) return hIcon;

				hIcon =  PWin32.LoadIcon(IntPtr.Zero,(IntPtr) SystemIcon.IDI_WINLOGO);
				if (!IntPtr.Zero.Equals(hIcon)) return hIcon;

				throw new System.ComponentModel.Win32Exception(); 

			}
		}
		#endregion

		#region ScreenShot
		private IntPtr _dc = IntPtr.Zero ;
		internal IntPtr DC 
		{
			get
			{
				_dc = PWin32.GetWindowDC(this.Handle);
				if (_dc== IntPtr.Zero ){ throw new System.ComponentModel.Win32Exception(); }
				System.GC.ReRegisterForFinalize(this);
				return _dc;
			}
		}

		internal bool ThumbnailCallback() 
		{
			return false;
		}


		//
		// Not working yet
		// look at http://weblogs.asp.net/justin_rogers/archive/2004/10/07/239189.aspx for more help
		//
		private Image _screenshot = null;
		internal Image ScreenShot 
		{
			get 
			{
				if (_screenshot == null) 
				{
					Image.GetThumbnailImageAbort UnUsedDel = new Image.GetThumbnailImageAbort(ThumbnailCallback);

					IntPtr  wndHDC = PWin32.GetWindowDC(this.Handle);
					if (wndHDC == IntPtr.Zero) { throw new System.ComponentModel.Win32Exception(); }

					//  create compatibile capture context and bitmap
					IntPtr  capHDC = GDI32.CreateCompatibleDC(wndHDC);
					if (capHDC == IntPtr.Zero) { throw new System.ComponentModel.Win32Exception(); }
					
					IntPtr  capBMP = GDI32.CreateCompatibleBitmap(wndHDC, this.Size.Width ,this.Size.Height);
					
					//  make sure bitmap non-zero
					if (capBMP == IntPtr.Zero) 
					{// if no compatible bitmap
						Debug.WriteLine(new System.ComponentModel.Win32Exception().Message );
						PWin32.ReleaseDC(this.Handle ,wndHDC); //   release window context
						GDI32.DeleteDC(capHDC); //   delete capture context
						return null; //   return null bitmap
					}

					//  select compatible bitmap in compatible context
					//  copy window context to compatible context
					//  select previous bitmap back into compatible context
					IntPtr  prvHDC = (IntPtr)GDI32.SelectObject(capHDC,capBMP); 
					
					uint ret = GDI32.BitBlt(capHDC,0,0,this.Size.Width ,this.Size.Height ,wndHDC,0,0,GDI32.SRCCOPY); 
					if (ret==0) { throw new System.ComponentModel.Win32Exception(); }
					
					GDI32.SelectObject(capHDC,prvHDC);

					//  create GDI+ bitmap for window
					Bitmap  bmp = System.Drawing.Image.FromHbitmap(capBMP); 

					//  release window and capture resources
					PWin32.ReleaseDC(this.Handle ,wndHDC); // release window context
					GDI32.DeleteDC(capHDC); // delete capture context
					GDI32.DeleteObject(capBMP); // delete capture bitmap

					_screenshot = bmp;

				}

				return _screenshot;

			}
		}


		#endregion

		#region Methods, Restore()

		internal void Invalidate() 		{
			Rectangle  r = Rect;
			uint ret;
			RECT R = RECT.FromXYWH(r.X,r.Y,r.Width,r.Height);
			ret = PWin32.InvalidateRect(Handle,R,true);
			if (ret==0) { throw new System.ComponentModel.Win32Exception(); }
			ret = PWin32.InvalidateRect(Handle,R,false);
			if (ret==0) { throw new System.ComponentModel.Win32Exception(); }
			ret=PWin32.InvalidateRect(Handle,R,false);
			if (ret==0) { throw new System.ComponentModel.Win32Exception(); }
			ret=PWin32.InvalidateRect(Handle,R,true);
			if (ret==0) { throw new System.ComponentModel.Win32Exception(); }
		}

		public void Resize(Size sz) 
		{
			bool wasMaxed=false;
			if (this.Maximised) 
			{
				this.Maximised = false;
				wasMaxed = true;
			}
			Rectangle r  = this.Rect;
			if (wasMaxed) 
			{
				r.X= r.Y =0;
			}
			uint ret = PWin32.MoveWindow(Handle,r.X,r.Y,sz.Width,sz.Height,true);
			if (ret==0) { throw new System.ComponentModel.Win32Exception(); }
		}

		public override string ToString()
		{
			return this.hWnd.ToInt32().ToString("x") + " " + this.Text  + " (" + this.ClassName  + ") " ;
		}

		private uint _threadID;
		private bool _gotThreadID;
		private uint ThreadID 
		{
			get 
			{
				if (!_gotThreadID)
				{

					uint proc=0;
					_threadID= PWin32.GetWindowThreadProcessId(Handle,proc);
					_gotThreadID= true;
				}
				return _threadID;
			}			
		}

		private void BringWindowToTop( IntPtr w)
		{
			CommandTS.WVerbose("Bring window to top");
			bool res = PWin32.BringWindowToTop(w);
			if (!res) 
			{
				CommandTS.WError("Failed on BringWindow to top");
				throw new System.ComponentModel.Win32Exception(); 
			}
		}

		private uint AttachThreadInput(IntPtr foregroundWnd)
		{
			string msg = "Attach thread input";
			if (foregroundWnd == ApplicationHandle)
			{
				msg += " to self";
			}
			CommandTS.WVerbose(msg);
			uint fThreadID = PWin32.GetWindowThreadProcessId(foregroundWnd,0);
			bool res = PWin32.AttachThreadInput(fThreadID,ThreadID,true);
			if (!res)
			{
				CommandTS.WError("failed to attach thread-No LastErrorInfo avail");
				return 0;
			}
			return fThreadID;
		}
		private void SetActiveWindow(IntPtr w)
		{
			CommandTS.WVerbose("Set Active Window");
			if (PWin32.SetActiveWindow(Handle) == IntPtr.Zero)
				W32ExMsg("failed Set Active Window");
		}

		private void DetachInput(uint fThreadID)
		{
			CommandTS.WVerbose("Detach input");
			bool res = PWin32.AttachThreadInput(fThreadID,ThreadID,false);
			if (!res)
			{
				CommandTS.WError("failed to detach thread" );
			}
		}

		private void RestoreIconic()
		{
			bool r;
			if (this.Iconic) 
			{
				CommandTS.WVerbose("Window is Iconic");
				if ( (this.ExtendedWindowStyle & WS_EX.LAYERED) != 0) 
				{
					CommandTS.WVerbose("Window is Layered");
					PWin32.SetWindowLongPtr(Handle,(int)GWL.EXSTYLE, 
						new IntPtr((long) 
						((int) this.ExtendedWindowStyle & ~((int) WS_EX.LAYERED))));
				}
				r = PWin32.ShowWindow(Handle,(int)SW.RESTORE);
			}
			else
			{
				r = PWin32.ShowWindow(Handle,(int)SW.SHOW);
			}
			CommandTS.WVerbose("Result of Show Window()=" + r.ToString());
		}

		private void W32ExMsg(string msg)
		{
			
			Win32Exception ex = new Win32Exception();
			CommandTS.WError(msg + " (" + ex.NativeErrorCode.ToString() + ") "+ ex.Message);
		}

		/// <summary>
		/// Restores and Brings the window to the front, 
		/// assuming it is a visible application window.
		/// </summary>
		public void Restore() 
		{
			CommandTS.WVerbose("Restore()");
			Trace.Indent();
			CommandTS.WVerbose("Window = " + this.ToString());
			if (!IsValidWindow) 
			{
				CommandTS.WWarning("Invalid Window");
				return;
			}

			ModalWindows mw = new ModalWindows(Handle);
			mw.GetWindows();
			if (mw.Items.Count > 0)
			{
				Debug.WriteLine(mw[0].ToString());
				mw[0].Restore();
				return;
			}

			try
			{
				AttachThreadInput(ApplicationHandle);
				PWin32.SetFocus(ApplicationHandle);

				PWin32.SwitchToThisWindow(Handle,true);
				IntPtr ForegroundWnd =PWin32.GetForegroundWindow();
				if(ForegroundWnd != Handle) 
				{
					uint fThreadID = AttachThreadInput(ForegroundWnd);
					if (fThreadID != 0)
					{
						if (PWin32.SetFocus(Handle) == IntPtr.Zero)
						{
							W32ExMsg("Failed to SetFocus");
						}
						DetachInput(fThreadID);
					}
					RestoreIconic();
				}
			}
			finally
			{
				Trace.Unindent();
			}
		}
		public void SendToBack() 
		{
			uint ret = PWin32.SetWindowPos(Handle,(IntPtr)HWNDZOrder.HWND_BOTTOM,0,0,0,0,0);
			if (ret==0) { throw new System.ComponentModel.Win32Exception(); }

		}

		public static WindowItem Desktop 
		{
			get 
			{
				WindowItem wi = new WindowItem(PWin32.GetDesktopWindow());
				return wi;
			}
		}


		#endregion

		#region TaskVisible

		/// <summary>
		/// Is this window visible in the alt-tab list.
		/// </summary>
		internal bool TaskVisible 
		{
			get 
			{
                if (!Visible) return false;
                // Order of checks matters

                //Rule 1
                if (!IsOwnerExToolWindow && IsExToolWindow && IsPopUp && !IsExAppWindow) return false;

                //Rule 2
                //	if (OwnerHandle!=IntPtr.Zero && !IsOwnerExToolWindow && !IsExToolWindow && !IsExAppWindow && !IsPopUp && IsExControlParent ) return false; 

                // Rule 3
                if (!IsOwnerExToolWindow || IsExAppWindow || (!IsExToolWindow && IsExControlParent)) return true;

                return false;



			}
		}

		internal string NotTaskVisibleReason
		{
			get 
			{
				string reason;
				if (TaskVisible) return "";
				if (!Visible ) return "Not Visible";
				if (!IsOwnerExToolWindow && IsExToolWindow && IsPopUp && !IsExAppWindow) 
				{
					return reason = "Rule 1";
//					if (!!IsOwnerExToolWindow) reason += "IsOwnerExToolWindow";
//					if (!IsExToolWindow) reason += ", !IsExToolWindow";
//					if (!IsPopUp) reason += ", !IsPopUp";
//					if (!!IsExAppWindow) reason += ", IsExAppWindow";
//					return reason;
				}

//				if (OwnerHandle!=IntPtr.Zero && !IsOwnerExToolWindow && !IsExToolWindow && 
//					!IsExAppWindow && !IsPopUp && IsExControlParent ) 
//				{
//					return reason = "Rule 2";

//					if (OwnerHandle==IntPtr.Zero) reason += "OwnerHandle";
//					if (IsOwnerExToolWindow) reason += ", IsOwnerExToolWindow";
//					if (IsExToolWindow) reason += ", IsExToolWindow";
//					if (IsExAppWindow) reason += ", IsExAppWindow";
//					if (IsPopUp) reason += ", IsPopUp";
//					if (!IsExControlParent) reason+=", !IsExControlParent";
//
//					return reason;
//				}

				if (!(!IsOwnerExToolWindow || IsExAppWindow || (!IsExToolWindow && IsExControlParent)) )
				{
					reason = "Failed Rule3::";
					if (IsOwnerExToolWindow) reason +="IsOwnerExToolWindow";
					if (!IsExAppWindow) reason +=", !IsExAppWindow";
					if (!(!IsExToolWindow && IsExControlParent))
					{
						if (IsExToolWindow) reason +=", IsExToolWindow";
						if (!IsExControlParent) reason +=", !IsExControlParent";
					}

					return reason;
				}

				return "Rule 4";
			}
		}


		internal bool IsValidWindow
		{
			get
			{

				IntPtr dw;
				IntPtr lResult  = PWin32.SendMessageTimeout(Handle, (uint) WM.NULL, 0, 0,SMTO.ABORTIFHUNG, TIMEOUT, out dw);
				if (lResult != IntPtr.Zero) return(true);

				int error = Marshal.GetLastWin32Error();
				switch (error)
				{
					case 1460: /* ERROR_TIMEOUT */
					{
						return false;
					}
					case 1400: //ERROR_INVALID_WINDOW_HANDLE
					{
						return false;
					}
					case 0:
					{
						Debugger.Break();
						CommandTS.WError("Got error '0' in IsValidWindow - returning true");
						return true;
					}
					default:
					{
						Win32Exception ex = new Win32Exception(error,"");
						CommandTS.WError(String.Format("Got error {0} in IsValidWindow is {1}",error,ex.Message));
						return false;
					}
				}
			}
		}



		//TODO test AreAnyOwnersTaskVisible
		internal bool AreAnyOwnersTaskVisible 
		{
			get 
			{
				if (OwnerHandle == IntPtr.Zero ) return TaskVisible;
				return (TaskVisible || new WindowItem(OwnerHandle).AreAnyOwnersTaskVisible );
			}
		}

		#endregion

		#region Dispose and Finalize
		//Implement IDisposable.
		public void Dispose()  
		{
			Dispose(true);
			GC.SuppressFinalize(this); 
		}

		//protected virtual
		void Dispose(bool disposing) 
		{
			if (disposing) 
			{
				// Free other state (managed objects).
			}
			// Free your own state (unmanaged objects).
			// Set large fields to null.
			if ( _dc != IntPtr.Zero) 
			{
				if (PWin32.ReleaseDC(this.Handle,_dc) ==0) 
				{
					Debug.WriteLine("Failed to release DC");
				}
				_dc = IntPtr.Zero;
			}
		}

		// Use C# destructor syntax for finalization code.
		~WindowItem() 
		{
			// Simply call Dispose(false).
			Dispose (false);
		}
		#endregion

	}//class
} //ns
